Skip to content

Add "remove and withdraw all" option to order removal (#2024)#2722

Open
thedavidmeister wants to merge 8 commits into
mainfrom
2026-06-13-issue-2024
Open

Add "remove and withdraw all" option to order removal (#2024)#2722
thedavidmeister wants to merge 8 commits into
mainfrom
2026-06-13-issue-2024

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #2024

What the issue asked

Allow withdrawing an order's vault balances at the same time as removing the
order. Per the issue, withdrawal must not be automatic (orders can share
vaults), so the Remove button should become a context menu offering "remove"
and "remove and withdraw all vaults".

What changed (TS/webapp only — no contract/bytecode changes)

  • OrderDetail.svelte — when the new optional onRemoveAndWithdrawAll
    callback is provided, the standalone Remove button is replaced by a dropdown
    with two items: Remove (unchanged behaviour) and Remove and withdraw all
    vaults
    . When the callback is not supplied the original single Remove button
    renders, so existing callers are unaffected.
  • handleRemoveOrderAndWithdrawAll.ts (new webapp service) — mirrors
    handleVaultsWithdrawAll, but generates a single
    multicall([removeOrder, withdraw]) so the order removal and the vault
    withdrawals execute atomically in one transaction. The withdraw calldata comes
    from vaultsList.getWithdrawCalldata() and is only appended when the order
    actually has withdrawable (positive-balance) vaults — an order whose vaults
    are all empty is still removed. The multicall is built with viem's
    encodeFunctionData against the Raindex multicall(bytes[]) ABI (selector
    0xac9650d8, inherited from OpenZeppelin Multicall).
  • order detail +page.svelte — wires the new onRemoveAndWithdrawAll
    prop to the new service.

Tests (all discriminating)

  • handleRemoveOrderAndWithdrawAll.test.ts (7 tests):
    • asserts the exact multicall calldata bytes for [removeOrder, withdraw]
      and decodes it to prove the flat order (a swapped order or a missing withdraw
      changes the bytes — verified by mutation);
    • asserts an order with no withdrawable vaults produces a remove-only
      multicall and never even calls getWithdrawCalldata;
    • asserts the combined modal title, the remove-order transaction created on
      confirm with the right args, and that remove-calldata / withdraw-calldata /
      withdrawable-lookup errors each toast and abort without opening the modal.
  • OrderDetail.test.ts (4 new tests): the dropdown replaces the bare button
    when onRemoveAndWithdrawAll is set; the Remove item calls onRemove (and not
    the other); the Remove-and-withdraw item calls onRemoveAndWithdrawAll with
    the order and its vaultsList (and not onRemove) — verified by mutation; and
    the dropdown is hidden for inactive orders.

Verification

  • ui-components full suite: 640 passed (incl. 31 OrderDetail tests).
  • webapp full suite: 187 passed (incl. 7 new service tests).
  • npm run check: ui-components 0 errors; webapp 2 pre-existing errors only
    (PUBLIC_WALLETCONNECT_PROJECT_ID missing env var, in untouched files).
  • Lint clean on all touched files; Prettier applied.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a combined “Remove and withdraw all vaults” option for eligible orders.
    • When available, the order details view now shows a dropdown with separate removal actions.
  • Bug Fixes

    • Improved handling for inactive orders so removal actions are not shown.
    • Streamlined transaction flow so removal and vault withdrawal can be submitted together when applicable.
  • Tests

    • Added coverage for the new removal dropdown, combined transaction flow, and error handling.

Issue #2024 asks to offer withdrawing an order's vault balances at the same
time as removing the order, without making withdrawal automatic (orders can
share vaults).

In OrderDetail.svelte the standalone Remove button becomes a dropdown (when the
new optional onRemoveAndWithdrawAll callback is provided) offering:
- "Remove" (unchanged behaviour), and
- "Remove and withdraw all vaults".

The webapp wires the new option to handleRemoveOrderAndWithdrawAll, which
mirrors handleVaultsWithdrawAll but builds a single multicall([removeOrder,
withdraw]) so removal and withdrawal execute atomically. Withdraw calldata is
only included when the order actually has withdrawable (positive-balance)
vaults, so an empty order is still removed. When onRemoveAndWithdrawAll is not
supplied, OrderDetail renders the original single Remove button, so existing
callers are unaffected.

Tests:
- ui-components OrderDetail: new tests assert the dropdown replaces the bare
  button, that each item invokes the correct callback (Remove -> onRemove with
  the order; Remove-and-withdraw -> onRemoveAndWithdrawAll with the order and
  its vaultsList), and that the dropdown is hidden for inactive orders.
- webapp handleRemoveOrderAndWithdrawAll: new tests assert the exact multicall
  calldata bytes and decode it to prove the flat [removeOrder, withdraw] order,
  that an empty order produces a remove-only multicall (and never requests
  withdraw calldata), the combined modal title, the remove-order transaction on
  confirm, and that remove/withdraw/lookup errors toast and abort.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Jun 13, 2026
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The order detail UI now offers a remove dropdown with an optional withdraw-all action. A new webapp service builds the combined transaction, opens a confirmation modal, and the order route wires the handler into the page.

Changes

Remove-and-withdraw order flow

Layer / File(s) Summary
Order detail dropdown
packages/ui-components/src/lib/components/detail/OrderDetail.svelte, packages/ui-components/src/__tests__/OrderDetail.test.ts
OrderDetail adds onRemoveAndWithdrawAll, renders a dropdown when it is present, and the tests cover dropdown visibility, action selection, and inactive-order rendering.
Remove-and-withdraw service
packages/webapp/src/lib/services/handleRemoveOrderAndWithdrawAll.ts, packages/webapp/src/__tests__/handleRemoveOrderAndWithdrawAll.test.ts
handleRemoveOrderAndWithdrawAll builds remove-plus-withdraw multicall calldata, opens the confirmation modal, creates the transaction on confirm, and the tests cover calldata, modal, and error paths.
Order route wiring
packages/webapp/src/routes/orders/[chainId]-[raindex]-[orderHash]/+page.svelte
The order page imports handleRemoveOrderAndWithdrawAll, defines onRemoveAndWithdrawAll, and passes it to OrderDetail.

Sequence Diagram(s)

sequenceDiagram
  participant OrderDetail.svelte
  participant handleRemoveOrderAndWithdrawAll
  participant order
  participant vaultsList
  participant openTransactionConfirmationModal
  participant manager.createRemoveOrderTransaction
  OrderDetail.svelte->>handleRemoveOrderAndWithdrawAll: onRemoveAndWithdrawAll(raindexClient, order, vaultsList)
  handleRemoveOrderAndWithdrawAll->>order: getRemoveCalldata()
  handleRemoveOrderAndWithdrawAll->>vaultsList: getWithdrawableVaults()
  handleRemoveOrderAndWithdrawAll->>vaultsList: getWithdrawCalldata()
  handleRemoveOrderAndWithdrawAll->>openTransactionConfirmationModal: encoded multicall calldata
  openTransactionConfirmationModal->>manager.createRemoveOrderTransaction: txHash and order metadata
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

(_/)
(•.•) I hop through menus, bright and new,
/ >🍃 remove, then sweep the vaults clean through.
One call, two paths, a tidy deed,
A bunny nods: “yes, that’s the speed!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a remove-and-withdraw-all option to order removal.
Linked Issues check ✅ Passed The PR implements the requested optional withdraw-on-removal flow with a context menu and keeps plain removal available.
Out of Scope Changes check ✅ Passed The changes stay focused on the requested order-removal withdraw option and its supporting UI, service, and tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-06-13-issue-2024

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/ui-components/src/__tests__/OrderDetail.test.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/node_modules/eslint/lib/cli.js:410:24)
    at async main (/node_modules/eslint/bin/eslint.js:152:22)

packages/ui-components/src/lib/components/detail/OrderDetail.svelte

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/node_modules/eslint/lib/cli.js:410:24)
    at async main (/node_modules/eslint/bin/eslint.js:152:22)

packages/webapp/src/__tests__/handleRemoveOrderAndWithdrawAll.test.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: ESLint configuration in --config is invalid:

  • Unexpected top-level property "__esModule".

    at ConfigValidator.validateConfigSchema (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2177:19)
    at ConfigArrayFactory._normalizeConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3019:19)
    at ConfigArrayFactory._loadConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:21)
    at ConfigArrayFactory.loadFile (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
    at createCLIConfigArray (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)
    at new CascadingConfigArrayFactory (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3735:29)
    at new CLIEngine (/node_modules/eslint/lib/cli-engine/cli-engine.js:617:36)
    at new ESLint (/node_modules/eslint/lib/eslint/eslint.js:430:27)
    at Object.execute (/node_modules/eslint/lib/cli.js:410:24)
    at async main (/node_modules/eslint/bin/eslint.js:152:22)

  • 2 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister

thedavidmeister commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

screenshot

UI screenshot: "remove and withdraw all" option on order removal

thedavidmeister and others added 6 commits June 15, 2026 18:06
Restate two added test comments as present-tense facts about what the
test asserts now, dropping before/after and hypothetical-mutation framing.
Comment-only; no code or logic changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Union: take main's LocalDbSyncGate wrapper around OrderDetail AND keep
this branch's onRemoveAndWithdrawAll prop (not present on main yet).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui-components/src/lib/components/detail/OrderDetail.svelte`:
- Around line 159-175: The OrderDetail menu is showing “Remove and withdraw all
vaults” whenever onRemoveAndWithdrawAll exists, even for zero-balance
vaultsList. Update the conditional in OrderDetail.svelte around the
Dropdown/DropdownItem branch to also check that the order has withdrawable
balance before rendering the extra action, using the existing data.vaultsList
shape. Keep the plain Remove DropdownItem available for empty vaults, and if
needed align the label/disabled state so the UI does not imply withdrawable
funds when there are none.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 927d6d2b-0738-4ad7-b081-c7143b1f5977

📥 Commits

Reviewing files that changed from the base of the PR and between 0ee70ff and 073f41a.

📒 Files selected for processing (5)
  • packages/ui-components/src/__tests__/OrderDetail.test.ts
  • packages/ui-components/src/lib/components/detail/OrderDetail.svelte
  • packages/webapp/src/__tests__/handleRemoveOrderAndWithdrawAll.test.ts
  • packages/webapp/src/lib/services/handleRemoveOrderAndWithdrawAll.ts
  • packages/webapp/src/routes/orders/[chainId]-[raindex]-[orderHash]/+page.svelte

Comment on lines +159 to +175
{#if onRemoveAndWithdrawAll}
<Button
id="remove-order-menu"
data-testid="remove-order-menu"
aria-label="Remove order">
Remove
<ChevronDownOutline size="sm" class="ml-2" />
</Button>
<Dropdown placement="bottom-end" triggeredBy="#remove-order-menu">
<DropdownItem
on:click={() => onRemove(raindexClient, data)}
data-testid="remove-button">Remove</DropdownItem
>
<DropdownItem
on:click={() => onRemoveAndWithdrawAll(raindexClient, data, data.vaultsList)}
data-testid="remove-and-withdraw-all-button"
>Remove and withdraw all vaults</DropdownItem

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Only show the withdraw-all action when the order actually has withdrawable balance.

This branch only checks whether onRemoveAndWithdrawAll exists, so a zero-balance vaultsList still advertises "Remove and withdraw all vaults" even though the downstream handler degrades to a plain remove. The new OrderDetail.test.ts fixture uses that exact all-zero shape, so this misleading path is already being asserted. Gate the extra item on a withdrawable-balance check (or disable/rename it) and keep the plain remove action for empty vaults.

💡 Suggested change
+	const hasWithdrawableVaults = (vaultsList: RaindexVaultsList) =>
+		vaultsList.items.some((vault) => vault.balance > 0n);
...
-					{`#if` onRemoveAndWithdrawAll}
+					{`#if` onRemoveAndWithdrawAll && hasWithdrawableVaults(data.vaultsList)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{#if onRemoveAndWithdrawAll}
<Button
id="remove-order-menu"
data-testid="remove-order-menu"
aria-label="Remove order">
Remove
<ChevronDownOutline size="sm" class="ml-2" />
</Button>
<Dropdown placement="bottom-end" triggeredBy="#remove-order-menu">
<DropdownItem
on:click={() => onRemove(raindexClient, data)}
data-testid="remove-button">Remove</DropdownItem
>
<DropdownItem
on:click={() => onRemoveAndWithdrawAll(raindexClient, data, data.vaultsList)}
data-testid="remove-and-withdraw-all-button"
>Remove and withdraw all vaults</DropdownItem
const hasWithdrawableVaults = (vaultsList: RaindexVaultsList) =>
vaultsList.items.some((vault) => vault.balance > 0n);
{`#if` onRemoveAndWithdrawAll && hasWithdrawableVaults(data.vaultsList)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui-components/src/lib/components/detail/OrderDetail.svelte` around
lines 159 - 175, The OrderDetail menu is showing “Remove and withdraw all
vaults” whenever onRemoveAndWithdrawAll exists, even for zero-balance
vaultsList. Update the conditional in OrderDetail.svelte around the
Dropdown/DropdownItem branch to also check that the order has withdrawable
balance before rendering the extra action, using the existing data.vaultsList
shape. Keep the plain Remove DropdownItem available for empty vaults, and if
needed align the label/disabled state so the UI does not imply withdrawable
funds when there are none.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow withdrawing at the same time as canceling an order

1 participant